{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#r \"nuget: TorchSharp-cpu\"\n",
    "\n",
    "open TorchSharp\n",
    "open type torch\n",
    "open type TorchSharp.TensorExtensionMethods\n",
    "open type TorchSharp.torch.distributions"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Random Numbers and Distributions\n",
    "\n",
    "There is a rich set of random number generation APIs in TorchSharp. We've already seen the ones that are easiest to use: randn(), rand(), and randint(). Normal and uniform distributions are the foundation for many other random number features.\n",
    "\n",
    "Note that randint() will generate integers, and the default type is a 64-bit integer. Same goes for randperm()."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "torch.rand(10).print()\n",
    "torch.randn(10).print()\n",
    "torch.randint(100,[|10|]).print()\n",
    "torch.randperm(25L)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the `TorchSharp.torch.distributions` static class, there is a much richer collection of distributions. Unlike the three basic generators, these generators are organized as classes that you call a method named `sample()` to get a bunch of random number."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting the seed\n",
    "\n",
    "Like most random number libraries, TorchSharp allows you to set the seed used for random number generation. You should see the first series and the last being identical, while the one in the middle is different.\n",
    "\n",
    "One peculiarity about TorchSharp is that using the same initial seed will not lead to the same sequence of numbers when using a CPU vs. a GPU. You cannot reproduce results you had on a CPU by running things on a GPU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "torch.random.manual_seed(4711L)\n",
    "torch.rand(10).print()\n",
    "torch.random.manual_seed(17L)\n",
    "torch.rand(10).print()\n",
    "torch.random.manual_seed(4711L)\n",
    "torch.rand(10)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Coin Toss\n",
    "\n",
    "For example, to get a single-value sample of the Bernoulli distrubtion, which is a binary false/true, 0/1, yes/no, heads/tails generator, you do the following, passing in the probability of the result being '1':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let bern = Bernoulli(torch.tensor(0.5f))\n",
    "bern.sample().item<single>()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The element type of the sample will be determined by the element type of the probability tensor, so using precise number literal syntax is important.\n",
    "\n",
    "Usually, you want more than one value, you want a tensor-full of them. `sample()` takes as its arguments the size of the dimensions of the tensor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "bern.sample(3L,4L)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To help with sampling, there's a class called `Binomial` whcih will run a number of coin tosses and count the number of times the result is '1'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let bin = Binomial(torch.tensor(100), torch.tensor(0.25f))\n",
    "bin.sample().item<single>()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "bin.sample(3L,4L)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Categories\n",
    "\n",
    "In the coin toss scenario, there were two categories -- yes/no, true/false, 0/1, etc. A more general class of distributions support N different categories. The foundational class for that is called 'Categorical,' and it works just like Bernoulli. You tell it how many categories there are, the probabilities for those categories (it doesn't have to be even), and then you get your sample. The length of the probabilities tensor tells the Categorical class how many categories there are. The categories are represented as integers in the range [0..N[."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let cat = Categorical(torch.tensor([|0.1f; 0.7f; 0.1f; 0.1f|]))\n",
    "cat.sample(4L)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like 'Binomial' for binary distributions, there's a class 'Multinomial' for categorical distributions that aren't binary. Here, the category is denoted by the index into the tensor. For sample sizes of at least one dimension, the innermost dimension (the last index) representes the category. In other words, each row is a sample, each column is a category. The value in each cell is how many times (out of the total count specified) that the category was selected."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let mult = Multinomial(100, torch.tensor([|0.1f; 0.7f; 0.1f; 0.1f|]))\n",
    "mult.sample().print()\n",
    "mult.sample(5L).print()\n",
    "mult.sample(2L,3L)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Real-valued Distributions\n",
    "\n",
    "The majority of random distributions are concerned with real numbers, parameteried by either a min/max range, or the mean and standard deviation, or parameters specific to a distribution.\n",
    "\n",
    "The normal, a.k.a. Gaussian, distribution is the familiar bell-curve, where the likelihood of a value being selected is much higher closer to the mean. \n",
    "\n",
    "With 'torch.randn()' and 'torch.rand()', the mean is always zero, and the standard deviation always one. To alter them, you get the sample, multiply by the desired standard deviation, then add the desired mean (in that order).\n",
    "\n",
    "You can still do that when you are using the distribution classes, but they also allow you to pass in the parameters when creating the distribution class. This is convenient when you are passing the distribution to a function, which doesn't necessarily have to know anything about what kind of distrubition it is given, or its parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let foo (dist:Distribution) = dist.sample(4L,4L)\n",
    "\n",
    "let norm1 = Normal(torch.tensor(0.5f), torch.tensor(0.125f))\n",
    "let norm2 = Normal(torch.tensor(0.15f), torch.tensor(0.025f))\n",
    "\n",
    "(foo norm1).print()\n",
    "(foo norm2).print()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The same goes for uniformly distributed numbers -- there's a class parameterized by the boundaries: [low,high]."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let uni = Uniform(torch.tensor(10.0f), torch.tensor(17.0f))\n",
    "foo uni"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Those distributions are just the most basic ones; there's a number of more esoteric distributions that are beyond the scope of this tutorial to describe when to use. The usage patters are the same, though: create a distribution instance, and then call `sample()` to get a tensor filled with random numbers from the distribution."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Generator\n",
    "\n",
    "So far, all the random numbers have been using the default RNG. This is a process-wide generator, which is returned by the call to manual_seed() that we saw before. In the preceding examples, the return value was ignored. Once the generator has been captured, it can be used to parameterize random number generation. Most random number APIs take an optional generator argument. \n",
    "\n",
    "Usually, the generator is the last argument, defaulted to 'null'. Often, there are other parameters with default values that come before the generator, so it's a good idea to get into the habit of passing the generator instance by name."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let gen1 = torch.random.manual_seed(17L)\n",
    "torch.rand(2,3, generator=gen1).print()\n",
    "torch.randn(2,3, generator=gen1).print()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the default RNG goes a long way, but in complex scenarios that require specific control over random number sequences, having a generator object will be required. For example, in a multi-threaded application, there is no reproducibility, even with a manually set seed, if more than one thread is generating numbers. In that situation, it can make sense to have each thread have it's own generator object.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let gen2 = new torch.Generator(189UL)\n",
    "let gen3 = new torch.Generator(189UL)\n",
    "\n",
    "torch.rand(2L,3L, generator=gen2).print()\n",
    "torch.rand(2L,3L, generator=gen3).print()\n",
    "torch.rand(2L,3L, generator=gen2).print()\n",
    "torch.rand(2L,3L, generator=gen3)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the case of distribution instances, a generator object is passed in when creating the instance, not when sampling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "let norm1 = Normal(torch.tensor(0.5f), torch.tensor(0.125f), generator=gen2)\n",
    "norm1.sample(10L)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "file_extension": ".cs",
   "mimetype": "text/x-csharp",
   "name": "C#",
   "pygments_lexer": "csharp",
   "version": "9.0"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
